#include <WinSock2.h>
#include "../include/eGadu.h"
#include <string>
#include <Windows.h>
#include "../include/openssl/sha.h"
#include <iostream>
#include <ctime>

using namespace eGaduLib;
using namespace std;


/**
	@author hck
	@version 0.0.1
	@date 08.05.2010
*/


// ZMIENNE GLOBALNE
SOCKET sock;
bool sConnected = false;
bool stopLoop = false;
DWORD recvThreadId = 0;
DWORD pingThreadId = 0;
HANDLE pingThread = 0;
HANDLE recvThread = 0;
eGaduList *conList;
int conListLen = 0;
int loggedUin = 0;
bool bigRead = false;

char* resolveRetCode(EGADU_RET ret)
{
	string sret;
	switch(ret)
	{
	case EGADU_INVALIDARG:
		sret = "Invalid argument";
		break;
	case EGADU_UNKNOWNERR:
		sret = "Unknown error";
		break;
	case EGADU_NET_CORRPUTED:
		sret = "Net: connection corrupted";
		break;
	case EGADU_NET_LOGINFAIL:
		sret = "Net: login failed";
		break;
	case EGADU_NET_NOTAVAL:
		sret = "Net: connection not avaliable";
		break;
	default:
		sret = "Ok";
	}

	char *cRet = new char[sret.length() + 1];
	strcpy(cRet, sret.c_str());

	return cRet;
}

EGADU_RET getAvalHost(string *rHost)
{
	SOCKET tSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons((u_short)80);
	saddr.sin_addr.s_addr = ((in_addr*) (gethostbyname("appmsg.gadu-gadu.pl")->h_addr_list[0]))->s_addr;

	if(connect(tSock, (sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR)
		return EGADU_NET_NOTAVAL;

	string req = "GET /appsvc/appmsg_ver8.asp?fmnumber=100&lastmsg=0&version=8.0.0.7669 HTTP/1.1\r\n";
	req += "Connection: Keep-Alive\r\n";
	req += "Host: appmsg.gadu-gadu.pl\r\n\r\n";
	int len = req.length();

	send(tSock, req.c_str(), len, 0);

	char buf[128];
	recv(tSock, buf, 128, 0);

	req = buf;
	size_t pos1 = req.find("\r\n\r\n") + 4;
	req = req.substr(pos1);
	req = req.substr(4);
	pos1 = req.find(":8074");
	req = req.substr(0, pos1);

	*rHost = req;

	closesocket(tSock);

	return EGADU_OK;
}

char *gg_sha_hash(const char *password, int seed)
{
	SHA_CTX ctx;
	static char result[20];

	SHA1_Init(&ctx);
	SHA1_Update(&ctx, password, strlen(password));
	SHA1_Update(&ctx, &seed, sizeof(seed));
	SHA1_Final((unsigned char*)result, &ctx);

	return result;
}

eGadu::eGadu(void)
{
	WSAData wsa;
	WSAStartup(MAKEWORD(2,2), &wsa);

	connected = NULL;
	disconnected = NULL;
	listUpdateEvent = NULL;
	newMessageEvent = NULL;
}
eGadu::~eGadu(void)
{
	WSACleanup();
}


EGADU_RET eGadu::login(int num, char *password, int status, const char *description)
{
	sConnected = false;

	if(num <= 0 || password == NULL || strlen(password) <= 0 || status <= 0)
		return EGADU_INVALIDARG;

	string host;
	EGADU_RET r = getAvalHost(&host);
	if(r != EGADU_OK)
		return r;

	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons((u_short)8074);
	saddr.sin_addr.s_addr = ((in_addr*) (gethostbyname(host.c_str())->h_addr_list[0]))->s_addr;

	if(connect(sock, (sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR)
	{
		return EGADU_NET_NOTAVAL;
	}

	gg_welcome welcome;
	recv(sock, (char*)&welcome, sizeof(welcome), 0);
	if(welcome.hdr.type != GG_WELCOME)
	{
		closesocket(sock);
		return EGADU_NET_CORRPUTED;
	}

	gg_login80 login;
	login.uin = num;
	login.language[0] = 'p'; login.language[1] = 'l';
	login.hash_type = GG_LOGIN_HASH_SHA1;
	strcpy(login.hash, gg_sha_hash(password, welcome.seed));
	for(int i = 21; i < 64; i++)
	{
		login.hash[i] = '\0';
	}
	login.status = status;
	login.flags = 0;
	login.features = 0x00000367 | 0x00000010;
	login.local_ip = 0;
	login.local_port = 0;
	login.external_ip = 0;
	login.external_port = 0;
	login.image_size = (char)64;
	login.unknown1 = 0x64;
	login.version_len = 0x23;
	strcpy(login.version, "Gadu-Gadu Client build 10.0.0.10547");
	login.description_size = 0;
	login.description = 0;
	if(description != 0)
	{
		int size = strlen(description);
		login.description_size = size;
		login.description = new char[size + 1];
		strcpy(login.description, description);
		login.description[login.description_size] = '\0';
	}

	gg_header hdr;
	hdr.type = GG_LOGIN80;
	hdr.size = (sizeof(login) - sizeof(login.description)) + login.description_size;
	send(sock, (char*)&hdr, sizeof(hdr), 0);

	send(sock, (char*)&login, sizeof(login) - sizeof(login.description), 0);

	if(login.description_size > 0)
		send(sock, login.description, login.description_size, 0);

	gg_login80_re login_re;
	recv(sock, (char*)&login_re, sizeof(login_re), 0);
	if(login_re.hdr.type != GG_LOGIN80_OK)
	{
		closesocket(sock);
		return EGADU_NET_LOGINFAIL;
	}

	if(login.description_size > 0)
	{
		delete [] login.description;
		login.description = 0;
	}

	timeval tv;
	tv.tv_sec = 5;
	setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(struct timeval));

	sConnected = true;
	loggedUin = num;

	if(connected != NULL)
		connected();

	return EGADU_OK;
}


EGADU_RET eGadu::updateContactsList(eGaduList *list)
{
	if(list == 0)
		return EGADU_INVALIDARG;

    int listLen = list->getLength();

    if(listLen <= 0)
        return EGADU_INVALIDARG;

	if(sock == 0 || !sConnected)
		return EGADU_NET_NOTAVAL;


	gg_header hdr;
	hdr.type = GG_NOTIFY_LAST;
	hdr.size = sizeof(gg_notify) * listLen;
	send(sock, (char*)&hdr, sizeof(hdr), 0);

	gg_notify notify;
	for(int i = 0; i < listLen; i++)
	{
		notify.uin = list->getUser(i)->getUin();
		notify.type = list->getUser(i)->getType();
		send(sock, (char*)&notify, sizeof(notify), 0);
	}

	conList = list;
	conListLen = listLen;

    return EGADU_OK;
}


DWORD WINAPI RecvLoop(PVOID param)
{
	gg_header hdr;
	int recvd = 0;

	eGadu *gadu = (eGadu*)param;

	while(!stopLoop)
	{
		recvd = 0;
		recvd = recv(sock, (char*)&hdr, sizeof(hdr), 0);
		if(recvd > 0)
		{
			gadu->resolveHeader(hdr);
		}

		Sleep(10);
	}

	return 0;
}

DWORD WINAPI PingLoop(PVOID param)
{
	gg_header hdr;
	hdr.type = GG_PING;
	hdr.size = 0;

	while(!stopLoop)
	{
		Sleep(290000);
		send(sock, (char*)&hdr, sizeof(hdr), 0);
	}

	return 0;
}

void rslvStatus80(int size, eGadu *gadu)
{
    bigRead = true;

	int recvd = 0;
	do {
		int tmp = 0;
		gg_notify_reply80 reply;
		tmp = recv(sock, (char*)&reply, sizeof(reply) - sizeof(reply.desc), 0);
		if(tmp <= 0)
		{
		    bigRead = false;
			return;
		}
		recvd += tmp;

		if(reply.description_size > 0)
		{
			reply.desc = new char[reply.description_size + 1];
			tmp = recv(sock, reply.desc, reply.description_size, 0);
			if(tmp <= 0)
				break;
			recvd += tmp;
			reply.desc[reply.description_size] = '\0';
		}

		if(conList != NULL && conListLen > 0)
		{
			for(int i = 0; i < conListLen; i++)
			{
				if(conList->getUser(i)->getUin() == reply.uin)
				{
					conList->getUser(i)->setStatus(reply.status);
					if(reply.description_size > 0)
					{
						conList->getUser(i)->setDesc(reply.desc);
						delete [] reply.desc;
                    }

					if(gadu->listUpdateEvent != 0)
					{
						gadu->listUpdateEvent(conList, reply.uin);
                    }
				}
			}
		}

	} while(recvd < size);

	bigRead = false;
}

void rslvUserData(int size, eGadu *gadu)
{
	char *buf = new char[size + 1];

    bigRead = true;

	int recvd = 0;
	do {
		int tmp = recv(sock, buf + recvd, size - recvd, 0);
		if(tmp <= 0)
		{
		    bigRead = false;
			delete [] buf;
			return;
		}
		recvd += tmp;
	} while(recvd < size);

	int iSize = sizeof(int);
	int iSize2 = 2 * iSize;
	int index = 0;

	gg_user_data ud;
	memcpy(&ud, buf, iSize2);
	index += iSize2;

	for(int i = 0; i < ud.num; i++)
	{
		gg_user_data_user ud_user;
		memcpy(&ud_user, buf + index, iSize2);
		index += iSize2;

		for(int j = 0; j < ud_user.num; j++)
		{
			gg_user_data_user_attr attr;
			memcpy(&attr, buf + index, iSize);
			index += iSize;

			attr.name = new char[attr.name_size + 1];
			memcpy(attr.name, buf + index, attr.name_size);
			index += attr.name_size;

			memcpy(&attr.type, buf + index, iSize);
			index += iSize;
			memcpy(&attr.value_size, buf + index, iSize);
			index += iSize;

			attr.value = new char[attr.value_size + 1];
			memcpy(attr.value, buf + index, attr.value_size);
			index += attr.value_size;

			delete [] attr.name;
			delete [] attr.value;
		}
	}

	bigRead = false;

	delete [] buf;
}

void rslvDisconnectAck(int size, eGadu *gadu)
{
	if(size > 0)
	{
		char *buf = new char[size + 1];

		int recvd = 0;
		do {
			int tmp = recv(sock, buf + recvd, size - recvd, 0);
			if(tmp <= 0)
			{
				delete [] buf;
				return;
			}
			recvd += tmp;
		} while(recvd < size);
	}
}

void rslvRecvMsg(int size, eGadu *gadu)
{
    bigRead = true;

	int recvd = 0;
	gg_recv_msg80 rMsg;

	recvd += recv(sock, (char*)&rMsg, sizeof(rMsg) - 12, 0);

	int html_size = rMsg.offset_plain - (sizeof(rMsg) - 12);
	rMsg.html_message = new char[html_size + 1];
	recvd += recv(sock, rMsg.html_message, html_size, 0);

	int plain_size = rMsg.offset_attributes - ((sizeof(rMsg) - 12) - html_size);
	rMsg.plain_message = new char[plain_size + 1];
	recvd += recv(sock, rMsg.plain_message, plain_size, 0);

	int attr_size = size - recvd;
	rMsg.attributes = new char[attr_size + 1];
	recvd += recv(sock, rMsg.attributes, attr_size, 0);

	if(gadu->newMessageEvent != NULL)
	{
		gadu->newMessageEvent(rMsg.sender, rMsg.plain_message, rMsg.html_message);
	}

	bigRead = false;

	delete [] rMsg.attributes;
}

void rslvSendMsgAck(int size, eGadu *gadu)
{
	gg_send_msg_ack smAck;
	int recvd = recv(sock, (char*)&smAck, sizeof(smAck), 0);
	if(recvd != size)
	{
		char *buf = new char[size - recvd];
		recv(sock, buf, size - recvd, 0);
		delete [] buf;
	}
}

void rslvXmlData(int size, eGadu *gadu)
{
	char *buf = new char[size + 1];
	int recvd = 0;

	bigRead = true;

	do {
		int tmp = recv(sock, buf + recvd, size - recvd, 0);
		if(tmp <= 0)
		{
		    bigRead = false;
			delete [] buf;
			return;
		}

		recvd += tmp;
	} while(recvd < size);

    bigRead = false;

	delete [] buf;
}

void eGadu::resolveHeader(gg_header hdr)
{
	if(hdr.type == GG_NOTIFY_REPLY80 || hdr.type == GG_STATUS80)
	{
		rslvStatus80(hdr.size, this);
	}
	else if(hdr.type == GG_USER_DATA)
	{
		rslvUserData(hdr.size, this);
	}
	else if(hdr.type == GG_DISCONNECT_ACK)
	{
		rslvDisconnectAck(hdr.size, this);
	}
	else if(hdr.type == GG_RECV_MSG80)
	{
		rslvRecvMsg(hdr.size, this);
	}
	else if(hdr.type == GG_SEND_MSG_ACK)
	{
		rslvSendMsgAck(hdr.size, this);
	}
	else if(hdr.type == GG_XML_DATA || hdr.type == GG_XML_EVENT)
	{
		rslvXmlData(hdr.size, this);
	}
	else if(hdr.type == GG_PONG)
	{
	}
	else
	{
		cout << "TYPE: " << hdr.type << " SIZE: " << hdr.size << endl;
	}
}

EGADU_RET eGadu::startRecvLoop()
{
	if(!sConnected)
		return EGADU_NET_NOTAVAL;

	stopLoop = false;

	pingThread = CreateThread(NULL, 0, PingLoop, NULL, 0, &pingThreadId);

	eGadu *ptr = this;
	recvThread = CreateThread(NULL, 0, RecvLoop, (LPVOID)ptr, 0, &recvThreadId);

	return EGADU_OK;
}


EGADU_RET eGadu::stopRecvLoop()
{
	stopLoop = true;

	if(pingThread != 0)
	{
	    TerminateThread(pingThread, 0);
	    pingThread = 0;
	}

	if(recvThread != 0)
	{
	    TerminateThread(recvThread, 0);
	    recvThread = 0;
	}

	return EGADU_OK;
}


EGADU_RET eGadu::changeStatus(EGADU_STATUS newStatus, const char *newDescription)
{
	if(!sConnected)
		return EGADU_NET_NOTAVAL;
	if(newStatus <= 0)
		return EGADU_INVALIDARG;

    gg_new_status80 nStat;

	int descLen = 0;
	if(newDescription != 0)
	{
		descLen = strlen(newDescription);
        nStat.description = new char[descLen + 1];
		strcpy(nStat.description, newDescription);
		nStat.description[descLen] = '\0';
	}

	nStat.status = newStatus;
	nStat.flags = 1;
	nStat.description_size = descLen;

	gg_header hdr;
	hdr.type = GG_NEW_STATUS80;
	hdr.size = (sizeof(nStat) - sizeof(nStat.description)) + nStat.description_size;

	send(sock, (char*)&hdr, sizeof(hdr), 0);

	send(sock, (char*)&nStat, sizeof(nStat) - sizeof(nStat.description), 0);

	if(descLen > 0)
	{
        send(sock, nStat.description, nStat.description_size, 0);
	}

	if(descLen > 0)
	{
		delete [] nStat.description;
		nStat.description = 0;
	}

	if(newStatus == EGADU_STATUS_NOT_AVAIL || newStatus == EGADU_STATUS_NOT_AVAIL_DESCR)
	{
	    stopRecvLoop();

	    if(disconnected != 0)
            disconnected();
    }

	return EGADU_OK;
}


void eGadu::closeConnection()
{
	stopRecvLoop();
	closesocket(sock);
	sock = 0;
	conList = 0;
	conListLen = 0;
	sConnected = false;
}


EGADU_RET eGadu::sendMessage(User *to, const char *msg)
{
	if(!sConnected)
		return EGADU_NET_NOTAVAL;
	if(to == NULL || msg == NULL || to->getUin() <= 0)
		return EGADU_INVALIDARG;

	gg_send_msg80 sMsg;
	time_t sec = time(NULL);

	sMsg.recipient = to->getUin();
	sMsg.seq = (int)sec;
	sMsg.Class = 0x0008;

	int plain_size = strlen(msg);
	sMsg.plain_msg = new char[plain_size + 1];
	strcpy(sMsg.plain_msg, msg);
	sMsg.plain_msg[plain_size] = '\0';

	string html_txt = "<span style=\"color:#000000; font-family:'MS Shell Dlg 2'; font-size:9pt; \">";
	html_txt += msg;
	html_txt += "</span>";

	int html_size = strlen(html_txt.c_str());
	sMsg.html_msg = new char[html_size + 1];
	strcpy(sMsg.html_msg, html_txt.c_str());
	sMsg.html_msg[html_size] = '\0';

	sMsg.attributes = new char[6];
	sMsg.attributes[0] = 0x02;
	sMsg.attributes[1] = 0x06;
	sMsg.attributes[2] = 0x00;
	sMsg.attributes[3] = 0x00;
	sMsg.attributes[4] = 0x00;
	sMsg.attributes[5] = 0x08;

	sMsg.offset_plain = (sizeof(sMsg) - 12) + html_size;
	sMsg.offset_attributes = sMsg.offset_plain + plain_size;

	gg_header hdr;
	hdr.type = GG_SEND_MSG80;
	hdr.size = (sizeof(sMsg) - 12) + html_size + plain_size + 6;
	send(sock, (char*)&hdr, sizeof(hdr), 0);

	send(sock, (char*)&sMsg, sizeof(sMsg) - 12, 0);
	send(sock, sMsg.html_msg, html_size, 0);
	send(sock, sMsg.plain_msg, plain_size, 0);
	send(sock, sMsg.attributes, 6, 0);

	delete [] sMsg.plain_msg;
	sMsg.plain_msg = 0;
	delete [] sMsg.html_msg;
	sMsg.html_msg = 0;
	delete [] sMsg.attributes;
	sMsg.attributes = 0;

	return EGADU_OK;
}
